package db

import (
	"dgo/MyProj/PrjDBUtils"
	"dgo/MyProj/PrjHttpServ"
	"dgo/MyProj/PrjHttpUtils"
	"dgo/goutils/gohttp"
	"dgo/goutils/utils"
	"dgo/plg/plgbasev0"
	"dgo/plg/plgnet"
	"fmt"
	"gitee.com/ymofen/supervalue"
	"net/http"
	"strings"
	"time"
)

type ProjDBHelperPlug struct {
	PlgBase
	firstRunFlag         byte
	dbres_file           string
	dbres_url            string
	dbres_prefix         string
	api_prefix           string
	default_datasourceid string

	http_allow_cross_access bool

	RegisterWebApiFunc     func(api_prefix string)
	UnRegisterWebApiFunc   func(api_prefix string)
	OnBeforeReloadConfFunc func(conf *wrapper.SuperValue)
	OnReloadConfFunc       func(conf *wrapper.SuperValue)
	OnFirstRunFunc         func()

	/***
	  uptype: 更新类型 DBCOMM_UPTYPE_APPEND, DBCOMM_UPTYPE_UPDATE
	  返回值:
	    ok: true, 表示有处理, false 表示没有处理
	    ret: 1:表示已经进行了响应, 会退出后续的处理
	  * 注意:
	     如果在该事件中处理过程中, 出现了异常, 如果处于事务中将会被回滚
	*/
	OnAfterUpdateEvent func(dao *PrjDBUtils.DAOHelper, req *plgbasev0.PrjHttpRec, uptype int) (ok bool, ret interface{})

	/***
	  通用数据请求之前
	*/
	OnBeforeDBCommRequestEvent func(req *PlgHttpRec) bool
}

func NewProjDBHelperPlug() *ProjDBHelperPlug {
	rval := &ProjDBHelperPlug{}
	return rval
}

func (this *ProjDBHelperPlug) RegisterPrefixHttpFunc(prefix, uri string, handlefun func(req *PlgHttpRec)) {
	gohttp.HandleFunc(prefix+uri, func(w http.ResponseWriter, r *http.Request) {
		req := plgnet.NewPlgHttpRec(w, r)
		req.CheckDecodeBody()
		handlefun(req)
	})
}

func (this *ProjDBHelperPlug) NewDAOHelper(datasourceid string) *PrjDBUtils.DAOHelper {
	if len(datasourceid) == 0 {
		return PrjDBUtils.NewDAOHelper(this.default_datasourceid)
	} else {
		return PrjDBUtils.NewDAOHelper(datasourceid)
	}
}

/*
*

	  {
		  "id": "db-helper",
		  "type": "db.helper",
		  "conf": {
			"datasource":[
			  {"id":"kpl-vrs", "drivername":"mysql", "connstring":"admin:WXvrs@admin!@#1@tcp(119.96.188.188:3306)/gnss-vrs?charset=utf8&parseTime=true&loc=Local"},
			  {"id":"dcloud", "drivername":"mysql", "connstring":"admin:WXvrs@admin!@#1@tcp(119.96.188.188:3306)/dcloud?charset=utf8&parseTime=true&loc=Local"}
			],
			"http-allow-cross-access": true,  // 允许跨域访问
			"db-res-delay-secs": 1,   // 延迟加载
			"db-res-file":"db.res",
			"db-res-prefix":"xxx",    // 所有id之前都加 xxx
			"db-res-url":"http://127.0.0.1:3000/plug/do?id=dcloud&accesstoken=kpl&action=pack&projectid=kpl-cloud",
	        "api-prefix":"/api/web/",  // 注册API地址 如果没有指定则使用插件方式访问, 尾部应该加入"/"
	        "default-datasourceid":"dcloud",
			"dbres":[
			  {
				"id":"vrs-batch", "conf":{
				   "$sql":{
					 "sql":"update caster_ntripsvr set fstate=$state$ where fsubnet='$subnet$'"
					}
				 }
			  }
			]
	  }
*/
func (this *ProjDBHelperPlug) ReloadConf(conf *wrapper.SuperValue) (err error) {
	if this.firstRunFlag == 0 && this.OnFirstRunFunc != nil {
		this.firstRunFlag = 1
		this.OnFirstRunFunc()
	}

	if this.OnBeforeReloadConfFunc != nil {
		this.OnBeforeReloadConfFunc(conf)
	}

	PrjDBUtils.LoadDataSourceConfig(conf.ValueByName("datasource"))

	conf.CheckExistsStrFunc("default-datasourceid", func(strv string) {
		this.default_datasourceid = strv
	})

	conf.CheckExistsStrFunc("api-prefix", func(strv string) {
		if this.api_prefix != strv {
			if len(this.api_prefix) > 0 {
				gohttp.UnHandle(this.api_prefix)
				gohttp.UnHandle(this.api_prefix + "sys/res/reload")
				if this.UnRegisterWebApiFunc != nil {
					this.UnRegisterWebApiFunc(this.api_prefix)
				}
			}
			this.api_prefix = strv
			if len(this.api_prefix) > 0 {
				gohttp.HandleFunc(this.api_prefix+"sys/res/reload", func(w http.ResponseWriter, r *http.Request) {
					n, err := this.reloadFromUrl()
					if err != nil {
						PrjHttpUtils.ResponseJSONValues(w, "code", -1, "msg", err.Error())
					} else {
						PrjHttpUtils.ResponseJSONValues(w, "code", 0, "n", n)
					}
				})
				gohttp.HandleFunc(this.api_prefix, func(w http.ResponseWriter, r *http.Request) {
					defer utils.PanicHandlerWithDebugString(fmt.Sprintf("%s-%s-dbcomm-handler", this.PlugId, strv))
					req := plgbasev0.NewPrjHttpRec(w, r)

					if this.http_allow_cross_access {
						PrjHttpUtils.AllowCrossAccessSet(w)
					}

					if req.ReqR.Method == "OPTIONS" {
						PrjHttpUtils.ResponseJSONValues(w, "code", 0)
						return
					}

					remainUrl := strings.TrimPrefix(r.URL.Path, this.api_prefix)
					remainUrl = strings.TrimPrefix(remainUrl, "/")
					resId, remainUrl := utils.CutPrefixByRune(remainUrl, 0, 0, '/')
					if len(resId) == 0 {
						req.SetValues("code", -1, "errcode", 1002, "msg", "非法的访问,缺少资源ID")
						req.ResponseAsJSON()
						return
					}

					req.CheckDecodeBody()
					req.Req.SetKeyValue("__resid", resId)
					if req.QryAsString("__datasource", "") == "" && len(this.default_datasourceid) > 0 {
						req.Req.SetKeyValue("__datasource", this.default_datasourceid)
					}

					if this.OnBeforeDBCommRequestEvent != nil {
						if !this.OnBeforeDBCommRequestEvent(req) {
							return
						}
					}

					_, errcode, err := this.OnHttpDBComm(remainUrl, PrjHttpServ.EMPTY_STRING, req)
					if errcode != 0 {
						req.SetValues("code", -1, "errcode", errcode, "msg", err.Error())
						req.CheckResponseAsJson()
					} else if err != nil {
						req.SetValues("code", -1, "msg", err.Error())
						req.CheckResponseAsJson()
					}
				})

				if this.RegisterWebApiFunc != nil {
					this.RegisterWebApiFunc(this.api_prefix)
				}
			}
		}
	})

	dbres_file_flag := 0
	conf.CheckStrFunc("db-res-file", func(strv string) {
		if this.dbres_file != strv {
			PrjHttpServ.DBResCleanEx(0, this.PlugId)
			this.dbres_file = strv
			dbres_file_flag = 1
		}
	})

	conf.CheckStrFunc("db-res-url", func(strv string) {
		this.dbres_url = strv
	})

	conf.CheckStrFunc("db-res-prefix", func(strv string) {
		if this.dbres_prefix != strv {
			PrjHttpServ.DBResCleanEx(0, this.PlugId)
			this.dbres_prefix = strv
			dbres_file_flag = 1
		}
	})

	conf.CheckExistsStrFunc("http-allow-cross-access", func(strv string) {
		this.http_allow_cross_access = utils.TryStrToBool(strv, false)
	})

	if dbres_file_flag == 1 {
		this.ReloadDBRes()
	}

	PrjHttpServ.RegisterDBResList(this.dbres_prefix, conf.ValueByName("dbres"), 1, this.PlugId)

	if this.OnReloadConfFunc != nil {
		this.OnReloadConfFunc(conf)
	}

	return
}

func (this *ProjDBHelperPlug) reloadFromUrl() (int, error) {
	err := PrjHttpServ.DownResFromServ(this.dbres_url, this.dbres_file, "")
	if err != nil {
		return 0, err
	} else {
		return PrjHttpServ.RegisterDBResFromFile(this.dbres_prefix, this.dbres_file, 0, this.PlugId)
	}
}

func (this *ProjDBHelperPlug) innerRelease() {
	if len(this.api_prefix) > 0 {
		gohttp.UnHandle(this.api_prefix)
		gohttp.UnHandle(this.api_prefix + "sys/res/reload")
		if this.UnRegisterWebApiFunc != nil {
			this.UnRegisterWebApiFunc(this.api_prefix)
		}
	}
	PrjHttpServ.DBResClean(this.PlugId)
}

func (this *ProjDBHelperPlug) ReloadDBRes() (int, error) {
	n, err := PrjHttpServ.RegisterDBResFromFile(this.dbres_prefix, this.dbres_file, 0, this.PlugId)
	if err != nil || n == 0 {
		time.AfterFunc(time.Second, func() { // 延迟加载
			this.reloadFromUrl()
		})
		return 0, nil
	}
	return n, err
}

func (this *ProjDBHelperPlug) OnHttpStatus(rec *plgbasev0.PrjHttpRec) (rval interface{}, errcode int, err error) {
	json := wrapper.NewSVObject()
	json.SetKeyValues("allow-cross", this.http_allow_cross_access,
		"default-datasource", this.default_datasourceid,
		"api-prefix", this.api_prefix,
	)
	PrjHttpUtils.JSON2TableRec(rec.RespW, "", json.AsJSONString(false))
	return
}

/*
*

	{
	   "datasource":"main",
	   "exec_resid":"xx",
	   "args":{
	       "$subnet$":"HGNET",
	       "$state$":0
	    }
	}
*/
func (this *ProjDBHelperPlug) OnHttpDo(rec *plgbasev0.PrjHttpRec) (rval interface{}, errcode int, err error) {
	if this.http_allow_cross_access {
		PrjHttpUtils.AllowCrossAccessSet(rec.RespW)
	}

	datasouceid := rec.QryAsString("datasource", "")
	if len(datasouceid) == 0 {
		return nil, 503, fmt.Errorf("请指定datasource")
	}
	exec_resid := rec.QryAsString("exec_resid", "")
	if len(exec_resid) > 0 {
		args := rec.Req.ForceByPath("args")
		rec.RangeURLParam(func(k, v string) bool {
			strs := strings.SplitN(k, ".", 2)
			if len(strs) == 2 && strs[0] == "args" {
				args.SetKeyString(strs[1], v)
			}
			return true
		})

		sql := PrjHttpServ.GetDBResSQL(exec_resid, "$sql", args)
		if len(sql) == 0 {
			return nil, 503, fmt.Errorf("%s对应的$sql没有进行配置", exec_resid)
		}
		n, errsql, err1 := PrjDBUtils.ExecuteBatchSQL(datasouceid, sql)
		if err1 != nil {
			rec.ResponseJSONValues("code", -1, "errsql", errsql, "msg", err1.Error())
			return
		} else {
			rec.ResponseJSONValues("code", 0, "upcnt", n)
		}
	}

	rec.ResponseJSONValues("code", -1, "msg", "nothing to do...")
	return
}

func (this *ProjDBHelperPlug) OnHttpDBComm(urltype string, resid string, rec *plgbasev0.PrjHttpRec) (isbreak bool, errcode int, err error) {
	isbreak = true

	rec.OnActionEx = func(action int, args ...interface{}) (bool, interface{}) {
		if action == PrjHttpServ.TYPE_GET_RESID {
			if len(resid) > 0 {
				return true, PrjHttpServ.GetDBRes(resid)
			}
			resid := args[0].(string)
			return true, PrjHttpServ.GetDBRes(this.dbres_prefix + resid)
		} else if action == PrjHttpServ.TYPE_INIT_DAO {
			dao := args[0].(*PrjDBUtils.DAOHelper)
			dao.OnRecUpSetValue = func(key *string, val *interface{}) {
				if *val == "$rand_8_key$" {
					*val = utils.RandKeyString(8)
				} else if *val == "$newid$" {
					*val = utils.RandKeyString(24)
				} else if *val == "$now$" {
					*val = time.Now()
				}
			}
			return true, nil
		} else if action == PrjHttpServ.TYPE_GET_VAR {
			varname := args[0].(string)
			var remainArgs []interface{}
			if len(args) > 1 {
				remainArgs = args[1:]
			} else {
				remainArgs = nil
			}
			return utils.InvokeFactoryVarFunc(varname, this, remainArgs...)
		} else if action == PrjHttpServ.TYPE_AFTER_UPDATE {
			if this.OnAfterUpdateEvent != nil {
				return this.OnAfterUpdateEvent(args[0].(*PrjDBUtils.DAOHelper), rec, args[2].(int))
			}
		}
		return false, nil
	}
	defer func() {
		rec.OnActionEx = nil
	}()

	if rec.QryAsString("__datasource", "") == "" && len(this.default_datasourceid) > 0 {
		rec.Req.SetKeyValue("__datasource", this.default_datasourceid)
	}

	if len(urltype) <= 1 || strings.HasPrefix(urltype, "/list") {
		errcode, err = PrjHttpServ.OnHttpDBCommQry(&rec.HttpRec)
		return
	} else if strings.HasPrefix(urltype, "/update") {
		rec.Req.SetKeyValues("__type", PrjHttpServ.DBCOMM_UPTYPE_UPDATE)
		errcode, err = PrjHttpServ.OnHttpDBCommUpdateRes(&rec.HttpRec)
		return
	} else if strings.HasPrefix(urltype, "/exupdate") {
		rec.Req.SetKeyValues("__type", PrjHttpServ.DBCOMM_UPTYPE_INSERTORUPDATE)
		errcode, err = PrjHttpServ.OnHttpDBCommUpdateRes(&rec.HttpRec)
		return
	} else if strings.HasPrefix(urltype, "/batchupdate") {
		errcode, err = PrjHttpServ.OnHttpDBCommBatchUpdateRes(&rec.HttpRec)
		return
	} else if strings.HasPrefix(urltype, "/add") {
		rec.Req.SetKeyValues("__type", PrjHttpServ.DBCOMM_UPTYPE_APPEND)
		errcode, err = PrjHttpServ.OnHttpDBCommUpdateRes(&rec.HttpRec)
		return
	} else if strings.HasPrefix(urltype, "/del") {
		rec.Req.SetKeyValues("__type", PrjHttpServ.DBCOMM_UPTYPE_DELETE)
		errcode, err = PrjHttpServ.OnHttpDBCommUpdateRes(&rec.HttpRec)
		return
	} else if strings.HasPrefix(urltype, "/exec") {
		errcode, err = PrjHttpServ.OnHttpDBCommExec(&rec.HttpRec)
		return
	} else if strings.HasPrefix(urltype, "/get") {
		rec.Req.SetKeyValue("__single", "data")
		errcode, err = PrjHttpServ.OnHttpDBCommQry(&rec.HttpRec)
		return

	}
	isbreak = false
	return
}

/*
**

	url:http://127.0.0.1:15101/plug/db-helper/get?__datasource=XXX&__resid=XXXX&args.id=1
	url:http://127.0.0.1:15101/plug/plugid/res/get?__datasource=XXX&__resid=XXXX&args.id=1
*/
func (this *ProjDBHelperPlug) OnHttpIndex(rec *plgbasev0.PrjHttpRec) (rval interface{}, errcode int, err error) {
	if this.http_allow_cross_access {
		PrjHttpUtils.AllowCrossAccessSet(rec.RespW)
	}

	{
		isbreak, errcode, err := this.OnHttpDBComm(rec.PlugPath, PrjHttpServ.EMPTY_STRING, rec)
		if isbreak {
			return nil, errcode, err
		}
	}

	{ // plug/plugid/res/get|list|update|del|exupdate|exec...
		prePlugPath := rec.PlugPath
		urlPath := strings.TrimPrefix(rec.PlugPath, "/")
		resid, remain := utils.CutPrefixByRune(urlPath, 0, 0, '/')
		if len(resid) == 0 {
			resid = remain
			remain = ""
		}
		resid = this.dbres_prefix + resid
		if PrjHttpServ.DBResExists(resid) {
			rec.PlugPath = remain
			rec.Req.SetKeyValue("__resid", resid) // 设定访问资源
			isbreak, errcode, err := this.OnHttpDBComm(rec.PlugPath, resid, rec)
			if isbreak {
				return nil, errcode, err
			}
			rec.PlugPath = prePlugPath
		}
	}

	if strings.HasPrefix(rec.PlugPath, "/reload") {
		PrjHttpServ.DBResCleanEx(0, this.PlugId)
		n, err1 := this.ReloadDBRes()
		if err1 != nil {
			rec.ResponseJSONValues("code", -1, "errcode", plgbasev0.ERR_DB_ERR, "msg", fmt.Sprintf("重载资源异常:%s", err.Error()))
		} else {
			rec.ResponseJSONValues("code", 0, "n", n)
		}
		return
	}
	// errcode, err = PrjHttpServ.OnHttpDBCommQry(&rec.HttpRec)
	return

}

func (this *ProjDBHelperPlug) OnObjectNotify(sender interface{}, notifytype int, data interface{}) (rval interface{}, err error) {
	if notifytype == utils.NOTIFY_TYPE_SETCONF {
		if conf, ok := data.(*wrapper.SuperValue); ok {
			err = this.ReloadConf(conf)
		} else {
			err = fmt.Errorf("非法的JSON配置")
		}
	} else if notifytype == utils.NOTIFY_TYPE_RELEASE {
		this.innerRelease()

	}
	return
}

func CreateProjDBHelperPlugFunc(args ...interface{}) (rval interface{}, err error) {
	obj := NewProjDBHelperPlug()
	if len(args) > 0 {
		if str, ok := args[0].(string); ok {
			obj.PlugId = str
		}
	}
	rval = obj
	return
}
